﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Xml;
using System.IO;
using PFAGame;
using System.Xml.Serialization;

namespace MapEditor 
{
    public class MapData : IXmlSerializable, IEnumerable<Module>
    {
        static private string           currentLoadedPath; 
        static public List<MapData>     list;

        public int[]                  generationParameters = new int[4];
        private List<Module>[]        sortedModules = new List<Module>[18];

        public String   name
        {
            set;
            get;
        }

        public Decor    decor;

        /// <summary>
        /// type 1 = iso, type 0 = platform
        /// </summary>
        public int      type;

        public int      sizeX
        {
            private set;
            get;
        }
        public int      sizeY
        {
            private set;
            get;
        }
        List<Module>    moduleList;

        public MapData()
        {
            moduleList = null;
            this.name = null;
            this.sizeX = 0;
            this.sizeY = 0;
            this.type = 0;
            this.decor = 0;
            for (int i  = 0; i < sortedModules.Length; i++)
                sortedModules[i] = new List<Module>();

        }

        public MapData(String name, int sizeX, int sizeY, int type, Decor decor)
        {
            moduleList = new List<Module>();
            this.name = name;
            this.sizeX = sizeX;
            this.sizeY = sizeY;
            this.type = type;
            this.decor = decor;
        }

        /// <summary>
        /// crée un nouveau module en fin de liste.
        /// </summary>
        /// <param name="name">nom du module à créer</param>
        public void newModule(String name)
        {
            moduleList.Add(new Module(name, sizeX, sizeY));
        }

        /// <summary>
        /// suprime un module avec son nom. Léve une exception s'il n'est pas dans la liste.
        /// </summary>
        /// <param name="name">nom du module à suprimer</param>
        public void deleteModule(String name)
        {
            foreach (Module m in moduleList)
            {
                if (m.name == name)
                {
                    moduleList.Remove(m);
                    return;
                }
            }
            throw new Exception("deleteModule: " + name + "not found in " + this.name);
        }

        /// <summary>
        /// obtient un module avec son nom. Léve une exception s'il n'est pas dans la liste.
        /// </summary>
        /// <param name="name">nom du module à obtenir</param>
        public Module getModule(String name)
        {
            foreach (Module m in moduleList)
                if (m.name == name)
                    return m;
            throw new Exception("getModule: " + name + " not found in " + this.name);
        }

        /// <summary>
        /// obtient le module d'une map plateforme.
        /// </summary>
        public Module getModule()
        {
            return moduleList[0];
        }

        /// <summary>
        /// obtient un module aléatoire à partir d'une orientation. léve une exception si ce module n'est pas implémenté
        /// </summary>
        public Module getModule(mazeSquare mazeSquare, Random rand)
        {
            //séléctionne la liste de module correspondant et random l'un de ses modules
            int val = (int)mazeSquare;
            List<Module> pickList;
            if (val <= 16)
                pickList = sortedModules[val];
            else
                pickList = ((mazeSquare & mazeSquare.start) != 0 ? sortedModules[16] : sortedModules[17]); //cas de map depart/arrivé
            if (pickList.Count > 0)
            {
                //retourne le module choisi
                int randomModuleIndex = rand.Next(pickList.Count);
                return pickList[randomModuleIndex];
            }
            else
                return moduleList[0];                   // s la liste est vide retourne le module par defaut
        }

        /// <summary>
        /// determine si un module porte déjà ce nom.
        /// </summary>
        /// <param name="name">nom testé</param>
        internal bool checkModuleNames(string name)
        {
            foreach (Module m in moduleList)
                if (m.name == name)
                    return true;
            return false;
        }

        /// <summary>
        /// monte le module ddans la liste.
        /// </summary>
        /// <param name="name">nom du module déplacé</param>
        internal void up(string name)
        {
            Module m = getModule(name);
            int i = moduleList.IndexOf(m);
            moduleList.Remove(m);
            moduleList.Insert(i - 1, m);
        }

        /// <summary>
        /// descend le module ddans la liste.
        /// </summary>
        /// <param name="name">nom du module déplacé</param>
        internal void down(string name)
        {
            Module m = getModule(name);
            int i = moduleList.IndexOf(m);
            moduleList.Remove(m);
            moduleList.Insert(i + 1, m);
        }

        /// <summary>
        /// suprime un module
        /// </summary>
        /// <param name="name">nom du module à suprimer</param>
        internal void delete(string name)
        {
            Module m = getModule(name);
            int i = moduleList.IndexOf(m);
            moduleList.Remove(m);
        }

        /// <summary>
        /// retourne une map avec son nom. léve une exception si elle n'est pas trouvé.
        /// </summary>
        /// <param name="name"> nom de la map à obtenir</param>
        public static MapData getMapByName(string name)
        {
            foreach (MapData m in list)
                if (name == m.name)
                    return m;
            throw new Exception("getMapByName: " + name + " not found in list of maps");
        }

        /// <summary>
        /// détermine si une map porte déjà ce nom
        /// </summary>
        /// <param name="name"> nom testé</param>
        public static bool checkMapNames(string name)
        {
            foreach (MapData m in list)
                if (name == m.name)
                    return true;
            return false;
        }

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            int deth = reader.Depth;
            name = reader.GetAttribute("name");
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "decor")
                    {
                        if (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Text)
                                decor = getDecorFromString(reader.Value);
                        }
                    }
                    else if (reader.Name == "type")
                    {
                        if (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Text)
                                type = int.Parse(reader.Value);
                        }
                    }
                    else if (reader.Name == "sizeX")
                    {
                        if (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Text)
                                sizeX = int.Parse(reader.Value);
                        }
                    }
                    else if (reader.Name == "sizeY")
                    {
                        if (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Text)
                                sizeY = int.Parse(reader.Value);
                        }
                    }
                    else if (reader.Name == "ArrayOfModule")
                    {
                        if (reader.NodeType == XmlNodeType.Element)
                        {
                            XmlSerializer xs = new XmlSerializer(typeof(List<Module>));
                            moduleList = xs.Deserialize(reader) as List<Module>;
                        }
                    }
                    else if (reader.Name == "generationParameters")
                        while (!(reader.LocalName == "generationParameters" && reader.NodeType == XmlNodeType.EndElement))
                        {
                            if (reader.Read() && reader.NodeType == XmlNodeType.Element)
                            {
                                if (reader.Name == "param_1")
                                {
                                    if (reader.Read())
                                    {
                                        if (reader.NodeType == XmlNodeType.Text)
                                            generationParameters[0] = Convert.ToInt32(reader.Value);
                                    }
                                }
                                else if (reader.Name == "param_2")
                                {
                                    if (reader.Read())
                                    {
                                        if (reader.NodeType == XmlNodeType.Text)
                                            generationParameters[1] = Convert.ToInt32(reader.Value);
                                    }
                                }
                                else if (reader.Name == "param_3")
                                {
                                    if (reader.Read())
                                    {
                                        if (reader.NodeType == XmlNodeType.Text)
                                            generationParameters[2] = Convert.ToInt32(reader.Value);
                                    }
                                }
                                else if (reader.Name == "param_4")
                                {
                                    if (reader.Read())
                                    {
                                        if (reader.NodeType == XmlNodeType.Text)
                                            generationParameters[3] = Convert.ToInt32(reader.Value);
                                    }
                                }
                            }
                            if (reader.EOF == true)
                                break;
                        }
                }
                if (deth == reader.Depth)
                {
                    reader.Read();
                    break;
                }
            }
            foreach (Module m in moduleList)
            {
                m.setSize(sizeX, sizeY); //je sais pas ce que c'est!

                //classe les modules
                if (m.start)
                {
                    sortedModules[16].Add(m);
                    continue;
                }
                if (m.end)
                {
                    sortedModules[17].Add(m);
                    continue;
                }
                int val = m.north ? 4 : 0;
                val += m.south ? 8 : 0;
                val += m.east ? 2 : 0;
                val += m.west ? 1 : 0;
                sortedModules[val].Add(m);
            }
        }

        private Decor getDecorFromString(string str)
        {
            if (str == "LABO")
                return Decor.LABO;
            else if (str == "FACTORY")
                return Decor.FACTORY;
            else if (str == "DESERT")
                return Decor.DESERT;
            else if (str == "FOREST")
                return Decor.FOREST;
            else
                throw new Exception("Decor inconnu");
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteAttributeString("name", name);
            writer.WriteElementString("decor", decor.ToString());
            writer.WriteElementString("type", type.ToString());
            writer.WriteElementString("sizeX", sizeX.ToString());
            writer.WriteElementString("sizeY", sizeY.ToString());
            writer.WriteStartElement("generationParameters");
            writer.WriteElementString("param_1", generationParameters[0].ToString());
            writer.WriteElementString("param_2", generationParameters[1].ToString());
            writer.WriteElementString("param_3", generationParameters[2].ToString());
            writer.WriteElementString("param_4", generationParameters[3].ToString());
            writer.WriteEndElement();
            XmlSerializer xs = new XmlSerializer(typeof(List<Module>));
            xs.Serialize(writer, moduleList);
        }

        public IEnumerator<Module> GetEnumerator()
        {
            foreach (Module m in moduleList)
                yield return m;
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        internal void resize(int x, int y)
        {
            foreach (Module m in moduleList)
                m.setSize(x, y);
            sizeX = x;
            sizeY = y;
        }

        /// <summary>
        /// charge la nouvelle liste de map, si elle a changé.
        /// </summary>
        /// <param name="path">path du fichier à charger</param>
        static public void load(string path)
        {
            if (currentLoadedPath == path)
                return;
            currentLoadedPath = path;

            XmlSerializer xs = new XmlSerializer(typeof(List<MapData>));
            using (StreamReader rd = new StreamReader(path))
            {
                MapData.list = (List<MapData>)xs.Deserialize(rd);
            }
        }
    }
}
